{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.846335Z",
     "start_time": "2025-01-16T06:45:02.383978Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.904540Z",
     "start_time": "2025-01-16T06:45:04.846335Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.907863Z",
     "start_time": "2025-01-16T06:45:04.904540Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:2])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:2])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585])\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 将数据分为训练集、验证集和测试集"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.942931Z",
     "start_time": "2025-01-16T06:45:04.907863Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train], #训练集\n",
    "    \"valid\": [x_valid, y_valid], #验证集\n",
    "    \"test\": [x_test, y_test], #测试集\n",
    "} #把3个数据集都放到字典中\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "source": [
    "type(x_train)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.946957Z",
     "start_time": "2025-01-16T06:45:04.943934Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "numpy.ndarray"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# fit和fit_transform的区别，fit是计算均值和方差，fit_transform是先fit，然后transform"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.952973Z",
     "start_time": "2025-01-16T06:45:04.946957Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "\n",
    "\n",
    "scaler = StandardScaler() #标准化\n",
    "scaler.fit(x_train) #fit和fit_transform的区别，fit是计算均值和方差，fit_transform是先fit，然后transform"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "source": [
    "np.array(1).shape"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.956326Z",
     "start_time": "2025-01-16T06:45:04.952973Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# PyTorch 模型通常需要浮点数类型的输入"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.963803Z",
     "start_time": "2025-01-16T06:45:04.956326Z"
    }
   },
   "source": [
    "#构建私有数据集，就需要用如下方式\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode] #x,y都是ndarray类型\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float() #from_numpy将NumPy数组转换成PyTorch张量\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1) #处理为多行1列的tensor类型,因为__getitem__切片时需要\n",
    "            \n",
    "    def __len__(self): #必须写\n",
    "        return len(self.x) #返回数据集的长度,0维的size\n",
    "    \n",
    "    def __getitem__(self, idx): #idx是索引，返回的是数据和标签，这里是一个样本\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "#train_ds是dataset类型的数据，与前面例子的FashionMNIST类型一致\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.966801Z",
     "start_time": "2025-01-16T06:45:04.963803Z"
    }
   },
   "cell_type": "code",
   "source": "type(train_ds)",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.HousingDataset"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 用来访问第一个样本中的数据和标签"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.972403Z",
     "start_time": "2025-01-16T06:45:04.966801Z"
    }
   },
   "source": "train_ds[1]",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 这表示第一个样本中的第一个第x个部分"
  },
  {
   "cell_type": "code",
   "source": [
    "train_ds[0][0]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.975745Z",
     "start_time": "2025-01-16T06:45:04.972403Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([ 0.8015,  0.2722, -0.1162, -0.2023, -0.5431, -0.0210, -0.5898, -0.0824])"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 11
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.979156Z",
     "start_time": "2025-01-16T06:45:04.976749Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "#放到DataLoader中的train_ds, valid_ds, test_ds都是dataset类型的数据\n",
    "batch_size = 16 #batch_size是可以调的超参数\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 12
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.981959Z",
     "start_time": "2025-01-16T06:45:04.979156Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.985779Z",
     "start_time": "2025-01-16T06:45:04.982963Z"
    }
   },
   "cell_type": "code",
   "source": "8*30+30+30*1+1",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "301"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# min_delta是最小变化，patience是多少个epoch没有提升就停止训练"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# @property 是 Python 中的一个装饰器（Decorator），用于将类的方法转换为属性（Property）"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.988940Z",
     "start_time": "2025-01-16T06:45:04.985779Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 15
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:45:04.991952Z",
     "start_time": "2025-01-16T06:45:04.988940Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 16
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# pbar.update(1)是更新进度条，pbar.set_postfix({\"epoch\": epoch_id})是更新进度条的文字"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:46:35.958615Z",
     "start_time": "2025-01-16T06:45:47.596197Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:#11610/16=726\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas) #得到预测值\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item() #转为cpu类型，item()是取值\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss) #根据验证集的损失来实现早停\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失,均方误差\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/72600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "aac23fa62ed941968ff486304622e176"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 80 / global_step 58080\n"
     ]
    }
   ],
   "execution_count": 17
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "record"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:47:16.396438Z",
     "start_time": "2025-01-16T06:47:16.283630Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 18
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-16T06:48:13.295991Z",
     "start_time": "2025-01-16T06:48:13.260417Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, test_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3253\n"
     ]
    }
   ],
   "execution_count": 20
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
